package uicgas.communication.impl;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.log4j.Logger;

import uicgas.communication.Communication;
import uicgas.globaldata.ClientTypes;
import uicgas.globaldata.ControlMessage;
import uicgas.globaldata.Messages;
import uicgas.globaldata.SelfAnnouncement;
import uicgas.pump.misc.Config;
import uicgas.tools.Assert;
import uicgas.tools.LogHelper;
import uicgas.tools.UicGasGeneralException;

/**
 * Abstract class for {@link Communication} interface<br>
 * It hides connection details of this Communication's final implementations
 * 
 * @author Hong Wang
 *
 */
public abstract class AbstractCommunicationImpl implements Communication
{
	private static final Logger LOGGER = LogHelper
			.getLogger(AbstractCommunicationImpl.class);

	private Socket connection = null;
	private ClientTypes clientType = null;

	protected AbstractCommunicationImpl(ClientTypes clientTypes)
	{
		Assert.notNull(clientTypes);
		this.clientType = clientTypes;
	}

	/**
	 * When ControlMessage came in from Server, process it here.
	 * @param messageIn
	 */
	abstract protected void processControlMessageFromServer(
			ControlMessage messageIn);

	/**
	 * Generate and return a unique ID representing the component;<br>
	 * If the ID is not unique in Server side, 
	 * this method may be invoke again, until unique ID got. 
	 * 
	 * @return unique ID representing the component
	 */
	abstract protected int generateUniqueComponentID();

	/**
	 * {@inheritDoc}
	 */
	public void start()
	{
		new Thread()
		{
			@Override
			public void run()
			{
				startInternal();
			}
		}.start();
	}

	private void startInternal()
	{
		establishConnectionWithStation();

		int id;

		do
		{
			id = generateUniqueComponentID();
			if (LOGGER.isDebugEnabled())
			{
				LOGGER.debug("Ready to register self to Station using ID[" + id
						+ "]. ");
			}

			registerSelfToStation(id);
		}
		while (!isSucessfullyRegistered(id));

		startToListenMessagesFromStation();
	}

	private boolean isSucessfullyRegistered(int id)
	{
		ObjectInputStream inputStream;
		try
		{
			inputStream = new ObjectInputStream(connection.getInputStream());
			ControlMessage messageIn = (ControlMessage) inputStream
					.readObject();

			Messages message = messageIn.getMessage();
			if (Messages.IDOK == message)
			{
				if (LOGGER.isDebugEnabled())
				{
					LOGGER.debug("Successfully register self to Station using ID["
							+ id + "]. ");
				}

				sucessfullyRegistered(id);

				return true;
			}
		}
		catch (IOException ioe)
		{
			LOGGER.error(LogHelper.LOG_LAYER_ONE + "Disconnected from Server["
					+ connection + "]. ");
			//			throw new UicGasGeneralException(ioe);
		}
		catch (ClassNotFoundException e)
		{
			throw new UicGasGeneralException(e);
		}

		if (LOGGER.isDebugEnabled())
		{
			LOGGER.debug("Failed register self to Station using ID[" + id
					+ "]. Trying next ID. ");
		}

		return false;
	}

	protected void sucessfullyRegistered(int id)
	{

	}

	/**
	 * Establish the Socket connected to the Server station
	 */
	private void establishConnectionWithStation()
	{
		if (LOGGER.isDebugEnabled())
		{
			LOGGER.debug("Begin to create Socket to Station. ");
		}
		try
		{
			connection = new Socket(Config.SERVER_IP,
					clientType.getConnectionPort());
		}
		catch (IOException e)
		{
			throw new UicGasGeneralException("Can not connect to Station["
					+ Config.SERVER_IP + ":" + clientType.getConnectionPort()
					+ "] ", e);
		}
		if (LOGGER.isDebugEnabled())
		{
			LOGGER.debug("End to create Socket to Station. " + connection);
		}
	}

	/**
	 * register this Communication to the server by using specified ID
	 * 
	 * @param id unique ID for this side client
	 */
	private void registerSelfToStation(int id)
	{
		SelfAnnouncement selfAnnouncement = new SelfAnnouncement(id, -1);
		ControlMessage controlMessage = new ControlMessage(
				clientType.getRegisterMessage(), selfAnnouncement);
		sendData(controlMessage);
	}

	/**
	 * Start to listen message from server, and process the message
	 */
	private void startToListenMessagesFromStation()
	{
		try
		{
			ControlMessage messageIn = null;
			ObjectInputStream inputStream = null;
			while (true)
			{
				inputStream = new ObjectInputStream(connection.getInputStream());
				messageIn = (ControlMessage) inputStream.readObject();
				if (LOGGER.isDebugEnabled())
				{
					LOGGER.debug(LogHelper.IN_COMING
							+ "Get ControlMessage from Server - ["
							+ messageIn.getMessage() + "], and send to ["
							+ clientType + "]. ");
				}

				processControlMessageFromServer(messageIn);
			}
		}
		catch (IOException ioe)
		{
			LOGGER.error(LogHelper.LOG_LAYER_ONE + "Disconnected from Server["
					+ connection + "]. ");
			//			throw new UicGasGeneralException(ioe);
		}
		catch (ClassNotFoundException e)
		{
			throw new UicGasGeneralException(e);
		}
	}

	@Override
	/**
	 * {@inheritDoc}
	 */
	public void sendData(ControlMessage messageOut)
	{
		if (connection == null)
		{
			LOGGER.error("Socket has not been established. ");

			return;
		}

		if (LOGGER.isDebugEnabled())
		{
			LOGGER.debug(LogHelper.OUT_GOING + "Begin to send ControlMessage["
					+ ToStringBuilder.reflectionToString(messageOut) + "]. ");
		}

		try
		{
			ObjectOutputStream outputStream = new ObjectOutputStream(
					connection.getOutputStream());
			outputStream.writeObject(messageOut);
		}
		catch (IOException e)
		{
			LOGGER.error(LogHelper.LOG_LAYER_ONE + "Disconnected from Server["
					+ connection + "]. ");
			//			throw new UicGasGeneralException(e);
		}

		if (LOGGER.isDebugEnabled())
		{
			LOGGER.debug(LogHelper.OUT_GOING + "End to send ControlMessage["
					+ ToStringBuilder.reflectionToString(messageOut) + "]. ");
		}

	}

}
